package com.freemodbus4j.network.server;

import com.freemodbus4j.core.thread.DefaultExecutorServiceFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.PropertyChangeSupport;


public class ModbusServer {

    private static final Logger logger = LoggerFactory.getLogger(ModbusServer.class);


    private final int port;
    private ServerBootstrap bootstrap ;
    private Channel parentChannel;
    private final transient PropertyChangeSupport propertyChangeSupport;
    private ModbusServer.CONNECTION_STATES connectionState;


    public ModbusServer(int port) {
        this.port = port;
        this.connectionState = ModbusServer.CONNECTION_STATES.DOWN;
        this.propertyChangeSupport = new PropertyChangeSupport(this);
    }

    protected void init(ChannelInitializer<SocketChannel> handler) throws Exception {
        try{
            int threads = Math.max(3, Runtime.getRuntime().availableProcessors() * 2 - 1);
            final EventLoopGroup bossGroup = new NioEventLoopGroup(threads, new DefaultExecutorServiceFactory("boss"));
            final EventLoopGroup workerGroup = new NioEventLoopGroup(threads, new DefaultExecutorServiceFactory("worker"));
            this.bootstrap = new ServerBootstrap();
            this.bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_REUSEADDR, true)
                    .option(ChannelOption.SO_BACKLOG, 1024).childOption(ChannelOption.TCP_NODELAY, true)
                    .childOption(ChannelOption.SO_KEEPALIVE, true).childHandler(handler);

            logger.info("modbus master started, bind port : {}" , this.port);

            this.parentChannel = this.bootstrap.bind(this.port).sync().channel();

            this.setConnectionState(ModbusServer.CONNECTION_STATES.LISTENING);

            this.parentChannel.closeFuture().addListener(future -> {
                workerGroup.shutdownGracefully();
                bossGroup.shutdownGracefully();
                this.setConnectionState(CONNECTION_STATES.DOWN);
            });
        }catch (Exception e){
            logger.error("init {}",e);
            throw new Exception("Connection Exception:" + e.getLocalizedMessage());
        }
    }

    protected void setConnectionState(ModbusServer.CONNECTION_STATES connectionState) {
        ModbusServer.CONNECTION_STATES oldConnectionState = this.connectionState;
        this.connectionState = connectionState;
        this.propertyChangeSupport.firePropertyChange("connectionState", oldConnectionState, connectionState);
    }

    public  enum CONNECTION_STATES {
        LISTENING,
        DOWN,
        CONNECTED;
    }
}
